﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.IO;
using DarkLakeTools.Configuration;
using System.Configuration;

namespace DarkLakeTools.DynamicImageProcessor
{
    public sealed class DynamicImageProcessor : IHttpHandler
    {

        #region Private Variables

        string mimeType = "";
        string cacheFile = "";
        #endregion

        /// <summary>
        /// Enables processing of HTTP Web requests by a custom HttpHandler that implements the <see cref="T:System.Web.IHttpHandler"/> interface.
        /// </summary>
        /// <param name="context">An <see cref="T:System.Web.HttpContext"/> object that provides references to the intrinsic server objects (for example, Request, Response, Session, and Server) used to service HTTP requests.</param>
        void IHttpHandler.ProcessRequest(HttpContext context)
        {

            try
            {
                DarkLakeTools.Configuration.DarkLakeSection config;

                config = (DarkLakeSection)ConfigurationManager.GetSection("darkLakeSection");

                DarkLakeTools.Configuration.DynamicImageProcessor configSection = config.DynamicImageProcessor;

                if (config == null || configSection == null)
                {
                    context.Response.Clear();
                    context.Response.Write("Configuration Error");
                    context.Response.StatusCode = 500;
                    context.Response.Flush();
                    context.Response.End();
                    return;
                }

                mimeType = configSection.DefaultMIMEType;

                Boolean useCache = configSection.EnableCache;
                // Setup the Response, Clear out all the fun stuff

                context.Response.Clear();
                context.Response.Cache.SetExpires(DateTime.Now.AddDays(30));
                context.Response.Cache.SetCacheability(HttpCacheability.Public);
                context.Response.Cache.SetValidUntilExpires(false);


                context.Response.BufferOutput = true;

                // get the orginally requested url and the attached height and width request. Then set to default if not present
                string imageUrl = context.Request.Path;
                int imageHeight = (context.Request.QueryString["h"] != null ? Convert.ToInt32(context.Request.QueryString["h"]) : 0);
                int imageWidth = (context.Request.QueryString["w"] != null ? Convert.ToInt32(context.Request.QueryString["w"]) : 0);

                // cropping
                int cropX = (context.Request.QueryString["x"] != null ? Convert.ToInt32(context.Request.QueryString["x"]) : -1);
                int cropY = (context.Request.QueryString["y"] != null ? Convert.ToInt32(context.Request.QueryString["y"]) : -1);

                //Check the file
                DateTime originalImageDate = System.IO.File.GetLastWriteTime(context.Server.MapPath(imageUrl));

                //Get the extenstion of the file
                string fileExt = GetFileExtension(imageUrl);


                // lets check and see if we've already used this resolution before
                if (useCache)
                {
                    if (cropX != -1 && cropX != -1)
                    {
                        cacheFile = (configSection.CacheDirectory + imageUrl.Replace("/", "\\")).Substring(0, (configSection.CacheDirectory + imageUrl.Replace("/", "\\")).LastIndexOf(fileExt)) + "\\" + imageHeight + "." + imageWidth + "." + cropX + "." + cropY + fileExt;
                    }
                    else
                    {
                        cacheFile = (configSection.CacheDirectory + imageUrl.Replace("/", "\\")).Substring(0, (configSection.CacheDirectory + imageUrl.Replace("/", "\\")).LastIndexOf(fileExt)) + "\\" + imageHeight + "." + imageWidth + fileExt;
                    }


                    if ((imageHeight != 0 && imageWidth != 0))
                        if (System.IO.File.Exists(cacheFile))
                            if (System.IO.File.GetLastWriteTime(cacheFile) == originalImageDate)
                            {
                                // sends the previously generated image
                                context.Response.ContentType = mimeType;
                                context.Response.WriteFile(cacheFile);
                                context.Response.Flush();
                                context.Response.End();
                                return;
                            }
                }
                // The Original Image object
                System.Drawing.Image imageFullSize;

                // Lets see if the requested file even exists
                if (!System.IO.File.Exists(context.Server.MapPath(imageUrl)))
                {
                    // it doesn't, so lets see if were using a not found image        
                    if (configSection.NotFoundImage != null)
                    {
                        // Are we going to resize the notfound image?
                        if (!configSection.NotFoundImage.Resize)
                        {
                            // sends the not found image
                            context.Response.ContentType = mimeType;
                            context.Response.WriteFile(context.Server.MapPath(Path.Combine(configSection.NotFoundImage.RelativePath, configSection.NotFoundImage.FileName)));
                            context.Response.Flush();
                            context.Response.End();
                            return;
                        }
                        else
                        {
                            imageUrl = Path.Combine(configSection.NotFoundImage.RelativePath, configSection.NotFoundImage.FileName);
                        }
                    }
                    else
                    {
                        // sends a 404
                        context.Response.Clear();
                        context.Response.StatusCode = 404;
                        context.Response.End();
                        return;
                    }
                }

                // lets check to see if requesting a custom resolution?

                if (imageHeight != 0 || imageWidth != 0)
                {
                    // they are, lets get the original image
                    imageFullSize = System.Drawing.Image.FromFile(context.Server.MapPath(imageUrl));

                    // now lets check if they are requesting an enlarged image (we don't do that)
                    if (imageFullSize.Height <= imageHeight || imageFullSize.Width <= imageWidth)
                    {
                        // they are, send the original image
                        context.Response.ContentType = mimeType;
                        context.Response.WriteFile(context.Server.MapPath(imageUrl));
                        context.Response.Flush();
                        context.Response.End();

                        return;
                    }
                    else
                    {
                        // ok it's smaller then the original, now we do support aspect ration based
                        // request, so they can only change height or width, and we'll do the calculation so 
                        // lets do the calculation. 
                        double aspectratio = Convert.ToDouble(Convert.ToDouble(imageFullSize.Width) / Convert.ToDouble(imageFullSize.Height));

                        if (imageHeight == 0)
                        {

                            imageHeight = Convert.ToInt32(imageWidth / aspectratio);
                        }
                        else if (imageWidth == 0)
                        {

                            imageWidth = Convert.ToInt32(imageHeight * aspectratio);
                        }

                        // Debug.Write(imageWidth.ToString() + " " + imageHeight.ToString());

                        // ok now we have the requested dimensions. lets check to see if we've already generated their request. 
                        if (useCache)
                        {

                            if (cropX != -1 && cropX != -1)
                            {
                                cacheFile = (configSection.CacheDirectory + imageUrl.Replace("/", "\\")).Substring(0, (configSection.CacheDirectory + imageUrl.Replace("/", "\\")).LastIndexOf(fileExt)) + "\\" + imageHeight + "." + imageWidth + "." + cropX + "." + cropY + fileExt;
                            }
                            else
                            {
                                cacheFile = (configSection.CacheDirectory + imageUrl.Replace("/", "\\")).Substring(0, (configSection.CacheDirectory + imageUrl.Replace("/", "\\")).LastIndexOf(fileExt)) + "\\" + imageHeight + "." + imageWidth + fileExt;
                            }

                            if (System.IO.File.Exists(cacheFile))
                                if (System.IO.File.GetLastWriteTime(cacheFile) == originalImageDate)
                                {
                                    // we have, lets send that one, no need to regenerate a file we've already generated. 
                                    context.Response.ContentType = mimeType;
                                    context.Response.WriteFile(cacheFile);
                                    context.Response.Flush();
                                    context.Response.End();

                                    imageFullSize.Dispose();

                                    return;

                                }
                        }

                        // ok now comes the dog and pony show, let's resize the image   
                        Bitmap mybmp = new Bitmap(imageWidth, imageHeight);
                        Graphics objGraphics = Graphics.FromImage(mybmp);

                        objGraphics.CompositingMode = CompositingMode.SourceOver;
                        objGraphics.CompositingQuality = CompositingQuality.GammaCorrected;

                        objGraphics.PixelOffsetMode = PixelOffsetMode.HighQuality;

                        objGraphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
                        objGraphics.SmoothingMode = SmoothingMode.HighQuality;


                        //Check to see if we want to crop the image or resize it
                        if (cropX != -1 && cropY != -1)
                        {
                            objGraphics.DrawImage(imageFullSize, new Rectangle(0, 0, mybmp.Width, mybmp.Height), cropX, cropY, mybmp.Width, mybmp.Height, GraphicsUnit.Pixel);

                        }
                        else
                        {
                            objGraphics.DrawImage(imageFullSize, 0, 0, mybmp.Width, mybmp.Height);

                        }

                        imageFullSize.Dispose();
                        System.Drawing.Imaging.Encoder qualityEncoder = System.Drawing.Imaging.Encoder.Quality;
                        System.Drawing.Imaging.Encoder colorEncoder = System.Drawing.Imaging.Encoder.ColorDepth;

                        EncoderParameter ratio = new EncoderParameter(qualityEncoder, (long)configSection.ImageQuality);
                        EncoderParameter light = new EncoderParameter(colorEncoder, 24L);

                        EncoderParameters codecParameters = new EncoderParameters(2);
                        codecParameters.Param[0] = ratio;
                        codecParameters.Param[1] = light;

                        Single Brightness = 0;
                        ColorMatrix BMatrix = new ColorMatrix(new Single[][]
                {new Single[] {1, 0, 0, 0, 0}, 
                 new Single[] {0, 1, 0, 0, 0}, 
                 new Single[] {0, 0, 1, 0, 0}, 
                 new Single[] {0, 0, 0, 1, 0}, 
                 new Single[] {Brightness, Brightness, Brightness, 0, 1}});


                        this.ApplyColorMatrix(ref mybmp, BMatrix);


                        // now we have to flush this to the outputstream              


                        if (config.DynamicImageProcessor.WaterMark != null)
                        {
                            Graphics grWatermark = Graphics.FromImage(new Bitmap(context.Server.MapPath(config.DynamicImageProcessor.WaterMark.WaterMarkImage)));

                            ImageAttributes waterAttributes = new ImageAttributes();

                            ColorMap waterColorMap = new ColorMap();

                            waterColorMap.OldColor = Color.FromArgb(255, 0, 255, 0);
                            waterColorMap.NewColor = Color.FromArgb(0, 0, 0, 0);

                            ColorMap[] remapTable = { waterColorMap };

                            waterAttributes.SetRemapTable(remapTable, ColorAdjustType.Bitmap);

                            float[][] colorMatrixElements = { 
                                                               new float[] {1.0f,  0.0f,  0.0f,  0.0f, 0.0f},
                                                               new float[] {0.0f,  1.0f,  0.0f,  0.0f, 0.0f},
                                                               new float[] {0.0f,  0.0f,  1.0f,  0.0f, 0.0f},
                                                               new float[] {0.0f,  0.0f,  0.0f,  0.3f, 0.0f},
                                                               new float[] {0.0f,  0.0f,  0.0f,  0.0f, 1.0f}
                                                            };

                            ColorMatrix wmColorMatrix = new
                                            ColorMatrix(colorMatrixElements);

                            waterAttributes.SetColorMatrix(wmColorMatrix,
                                                   ColorMatrixFlag.Default,
                                                     ColorAdjustType.Bitmap);


                            int xPosOfWm = 10;
                            int yPosOfWm = 10;

                           //  grWatermark.DrawImage(mybmp,0.0f,0.0f,0.0f,50,50, GraphicsUnit.Pixel, waterAttributes); 

                            //grWatermark.DrawImage(mybmp,new Rectangle(new Point(0, 0), new Size(30, 30)), 0, 0, GraphicsUnit.Pixel, waterAttributes); 
                            

                        }
                        else
                        {
                            if (fileExt == ".png")
                            {

                                //to save as png we need to use a memorystream in order to be able to seek
                                using (System.IO.MemoryStream memStream = new System.IO.MemoryStream())
                                {
                                    mybmp.Save(memStream, ImageFormat.Png);
                                    memStream.WriteTo(context.Response.OutputStream);
                                }
                            }
                            else
                            {
                                mybmp.Save(context.Response.OutputStream, this.GetEncoderInfo(mimeType), codecParameters);
                            }
                        }
                        // send it back to the client
                        context.Response.ContentType = mimeType;
                        context.Response.Flush();
                        context.Response.End();

                        if (useCache)
                        {
                            // check to make sure the cache directory exists
                            if (!System.IO.Directory.Exists(cacheFile.Substring(0, cacheFile.LastIndexOf("\\"))))
                                System.IO.Directory.CreateDirectory(cacheFile.Substring(0, cacheFile.LastIndexOf("\\")));
                            // save the image to the cache directory

                            try
                            {
                                if (fileExt == ".png")
                                {
                                    mybmp.Save(cacheFile, ImageFormat.Png);
                                    System.IO.File.SetLastWriteTime(cacheFile, originalImageDate);
                                }
                                else
                                {
                                    mybmp.Save(cacheFile, this.GetEncoderInfo(mimeType), codecParameters);
                                    System.IO.File.SetLastWriteTime(cacheFile, originalImageDate);
                                }
                            }
                            catch { }

                        }


                        objGraphics.Dispose();
                        mybmp.Dispose();


                        return;
                    }

                }
                else
                {
                    // if the file is there and no height or width changes are required. 
                    // send the original file
                    context.Response.ContentType = mimeType;
                    context.Response.WriteFile(context.Server.MapPath(imageUrl));
                    context.Response.Flush();
                    context.Response.End();
                }
            }
            catch (Exception ex)
            {
                context.Response.End();
            }

        }

        /// <summary>
        /// Applies the color matrix.
        /// </summary>
        /// <param name="myImage">My image.</param>
        /// <param name="myMatrix">My matrix.</param>
        private void ApplyColorMatrix(ref Bitmap myImage, ColorMatrix myMatrix)
        {
            ImageAttributes ImgAtt = new ImageAttributes();
            Bitmap bmpMatrix = new Bitmap(myImage.Width, myImage.Height);
            System.Drawing.Graphics grMatrix = System.Drawing.Graphics.FromImage(bmpMatrix);
            ImgAtt.SetColorMatrix(myMatrix);

            grMatrix.DrawImage(myImage, new Rectangle(0, 0, myImage.Width, myImage.Height), 0, 0, myImage.Width, myImage.Height, GraphicsUnit.Pixel, ImgAtt);
            myImage = bmpMatrix;
            grMatrix.Dispose();
            ImgAtt.Dispose();
        }

        /// <summary>
        /// Gets the encoder info.
        /// </summary>
        /// <param name="mime">The MIME.</param>
        /// <returns></returns>
        private ImageCodecInfo GetEncoderInfo(string mime)
        {
            ImageCodecInfo[] encoders;
            encoders = ImageCodecInfo.GetImageEncoders();
            foreach (ImageCodecInfo e in encoders)
            {
                if (e.MimeType == mime)
                    return e;
            }

            return null;

        }

        /// <summary>
        /// Gets the file extension.
        /// </summary>
        /// <param name="fileName">Name of the file.</param>
        /// <returns></returns>
        private string GetFileExtension(string fileName)
        {
            return Path.GetExtension(fileName);
        }

        #region IHttpHandler Members

        /// <summary>
        /// Gets a value indicating whether another request can use the <see cref="T:System.Web.IHttpHandler"/> instance.
        /// </summary>
        /// <value></value>
        /// <returns>true if the <see cref="T:System.Web.IHttpHandler"/> instance is reusable; otherwise, false.
        /// </returns>
        bool IHttpHandler.IsReusable
        {
            get { return false; }
        }

        #endregion
    }
}
